{
 "cells": [
  {
   "cell_type": "raw",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. _nb_custom:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Problem Definition"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In the following different ways of loading or implementing an optimization problem in our framework are discussed."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### By Class\n",
    "\n",
    "An optimization problem can be defined by inheriting from the *Problem* class. In the following the definition of a simple optimization problem with **one** objective and **two** constraints is considered. The problem has two constants, *const_1* and *const_2* which can be modified by initiating the problem with different parameters. By default, it consists of 10 variable and the lower and upper bounds are within $[-5, 5]$ for all variables. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import autograd.numpy as anp\n",
    "\n",
    "from pymoo.model.problem import Problem\n",
    "\n",
    "class MyProblem(Problem):\n",
    "\n",
    "    def __init__(self, const_1=5, const_2=0.1):\n",
    "\n",
    "        # define lower and upper bounds -  1d array with length equal to number of variable\n",
    "        xl = -5 * anp.ones(10)\n",
    "        xu = 5 * anp.ones(10)\n",
    "\n",
    "        super().__init__(n_var=10, n_obj=1, n_constr=2, xl=xl, xu=xu, evaluation_of=\"auto\")\n",
    "\n",
    "        # store custom variables needed for evaluation\n",
    "        self.const_1 = const_1\n",
    "        self.const_2 = const_2\n",
    "\n",
    "    def _evaluate(self, x, out, *args, **kwargs):\n",
    "        f = anp.sum(anp.power(x, 2) - self.const_1 * anp.cos(2 * anp.pi * x), axis=1)\n",
    "        g1 = (x[:, 0] + x[:, 1]) - self.const_2\n",
    "        g2 = self.const_2 - (x[:, 2] + x[:, 3])\n",
    "\n",
    "        out[\"F\"] = f\n",
    "        out[\"G\"] = anp.column_stack([g1, g2])\n",
    "\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "After creating a problem object the evaluate function can be called. The *return_values_of* variable can be overwritten to modify the list of returned parameters. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "problem = MyProblem()\n",
    "F, G, CV, feasible, dF, dG = problem.evaluate(anp.random.rand(100, 10),\n",
    "                                              return_values_of=[\"F\", \"G\", \"CV\", \"feasible\", \"dF\", \"dG\"])\n"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. _nb_problem_elementwise:"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Elementwise Evaluation**\n",
    "\n",
    "If the problem can not be executed by using matrix operations a serialized evaluation can be indicated by using the *elementwise_evaluation* flag. If *true*, then an outer loop is already implemented an *x* is just one individual."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class MyProblem(Problem):\n",
    "\n",
    "    def __init__(self, **kwargs):\n",
    "        super().__init__(n_var=2, n_obj=1, elementwise_evaluation=True, **kwargs)\n",
    "\n",
    "    def _evaluate(self, x, out, *args, **kwargs):\n",
    "        out[\"F\"] = x.sum()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### By String\n",
    "\n",
    "In our framework various test problems are already implemented and available by providing the corresponding problem name we have assigned to it. A couple of problems can be further parameterized by provided for instance the number of variables, constraints or other problem dependent constants."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {
    "code": "usage_problem.py",
    "section": "from_string"
   },
   "outputs": [],
   "source": [
    "from pymoo.factory import get_problem\n",
    "\n",
    "p = get_problem(\"dtlz1_-1\", n_var=20, n_obj=5)\n",
    "\n",
    "# create a simple test problem from string\n",
    "p = get_problem(\"Ackley\")\n",
    "\n",
    "# the input name is not case sensitive\n",
    "p = get_problem(\"ackley\")\n",
    "\n",
    "# also input parameter can be provided directly\n",
    "p = get_problem(\"dtlz1_-1\", n_var=20, n_obj=5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### By Function\n",
    "\n",
    "We recommend defining a problem by class because this provides you all the functionalities provided by the framework, such as implementing the pareto_front function or variable types and many more.\n",
    "However, many frameworks define a problem just by a function which is also supported in *pymoo* by the get_problem_from_func which takes the evaluation function and optional the boundaries as an input."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "from pymoo.model.problem import get_problem_from_func\n",
    "\n",
    "def my_evaluate_func(x, out, *args, **kwargs):\n",
    "\n",
    "    # define the objective as x^2\n",
    "    f1 = np.sum(np.square(x - 2), axis=1)\n",
    "    f2 = np.sum(np.square(x + 2), axis=1)\n",
    "    out[\"F\"] = np.column_stack([f1, f2])\n",
    "\n",
    "    # x^2 < 2 constraint\n",
    "    out[\"G\"] = np.sum(np.square(x - 1), axis=1)\n",
    "\n",
    "\n",
    "# load the problem from a function - define 3 variables with the same lower bound\n",
    "problem = get_problem_from_func(my_evaluate_func, xl=-10, xu=10, n_var=3)\n",
    "F, CV = problem.evaluate(np.random.rand(100, 3))\n",
    "\n",
    "# or define a problem with varying lower and upper bounds\n",
    "problem = get_problem_from_func(my_evaluate_func, xl=np.array([-10, -5, -10]), xu=np.array([10, 5, 10]))\n",
    "F, CV = problem.evaluate(np.random.rand(100, 3))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## API"
   ]
  },
  {
   "cell_type": "raw",
   "metadata": {
    "raw_mimetype": "text/restructuredtext"
   },
   "source": [
    ".. class:: pymoo.model.problem.Problem\n",
    "\n",
    "   .. automethod:: evaluate(X)\n",
    "   .. automethod:: pareto_front(X)\n"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
